/********************************************************************************
  * @file    preferencewidget.cpp
  * @author  Lun Li
  * @version V2.2.0
  * @date    2022.7.8
  * @brief   This file provides all PreferenceWidget functions.
  ******************************************************************************/

#include "preferencewidget.h"

PreferenceWidget::PreferenceWidget(ProgramConfig *const config, QWidget *parent)
    : QWidget{parent},
      m_config(config)
{
    setAttribute(Qt::WA_DeleteOnClose);

    m_propertyChanged = false;

    categoriesList = new QListWidget;
    categoriesLabel = new QLabel(tr("Categories:"));
    stackedWidget = new QStackedWidget;
    fontConfigWidget = new QWidget;
    languageConfigWidget = new QWidget;
    buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Apply | QDialogButtonBox::Cancel);

    categoriesList->addItems(QStringList() << tr("Compile Tools") << tr("Fonts") << tr("Languages"));
    categoriesList->setEditTriggers(QListWidget::NoEditTriggers);

    QScrollArea *toolArea = new QScrollArea;
    toolConfigWidget = new QWidget;
    toolConfigWidget->setLayout(new NoMarginVBoxLayout);

    QScrollArea *fontArea = new QScrollArea;
    fontConfigWidget = new QWidget;
    fontConfigWidget->setLayout(new NoMarginVBoxLayout);

    QScrollArea *languageArea = new QScrollArea;
    languageConfigWidget = new QWidget;
    languageConfigWidget->setLayout(new NoMarginVBoxLayout);

    initializeToolConfig();
    initializeFontConfig();
    initializeLanguageConfig();
    toolArea->setWidget(toolConfigWidget);
    fontArea->setWidget(fontConfigWidget);
    languageArea->setWidget(languageConfigWidget);
    stackedWidget->addWidget(toolArea);
    stackedWidget->addWidget(fontArea);
    stackedWidget->addWidget(languageArea);

    connect(categoriesList, &QListWidget::currentRowChanged, stackedWidget, &QStackedWidget::setCurrentIndex);
    connect(buttonBox, &QDialogButtonBox::clicked, this, [=](QAbstractButton *button) {
        if (button == buttonBox->button(QDialogButtonBox::Ok)) {
            storeSetting();
            close();
        } else if (button == buttonBox->button(QDialogButtonBox::Apply)) {
            storeSetting();
        } else {
            if (m_propertyChanged) {
                int result = QMessageBox::question(this, tr("Warning"),
                                                      tr("Setting has been modified. Save changes?"),
                                                      QMessageBox::Save | QMessageBox::No | QMessageBox::Cancel,
                                                      QMessageBox::Save);
                if (result == QMessageBox::Save) {
                    storeSetting();
                    close();
                }
                else if (result == QMessageBox::No) {
                    close();
                }
            } else {
                close();
            }
        }
    });

    QWidget *rightWidget = new QWidget;
    rightWidget->setLayout(new NoMarginVBoxLayout);
    rightWidget->layout()->addWidget(stackedWidget);
    rightWidget->layout()->addWidget(buttonBox);
    QWidget *leftWidget = new QWidget;
    leftWidget->setLayout(new NoMarginVBoxLayout);
    leftWidget->layout()->addWidget(categoriesLabel);
    leftWidget->layout()->addWidget(categoriesList);

    NoMarginHBoxLayout *hLayout = new NoMarginHBoxLayout;
    setLayout(hLayout);
    hLayout->addWidget(leftWidget);
    hLayout->addWidget(rightWidget);
    hLayout->setStretch(0, 1);
    hLayout->setStretch(1, 5);
    setWindowTitle(tr("Preference"));
    setWindowFlags(Qt::Dialog);

    categoriesList->setCurrentRow(0);
    m_propertyChanged = false;
    buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
}

void PreferenceWidget::initializeToolConfig()
{
    compileToolPathEdit = new ShowDirLineEdit(tr("Iverilog Tool Path:"), tr("Browse"), this);
    connect(compileToolPathEdit, &ShowDirLineEdit::textChanged, this, &PreferenceWidget::on_propertyChanged);
    connect(compileToolPathEdit->button(), &QPushButton::clicked, this, [=]() {
        QString dir = QFileDialog::getExistingDirectory(this, tr("Select Iverilog Tool Path"), m_config->lastOpenPath());
        if (dir.isEmpty()) {
            return;
        }
        m_config->setLastOpenPath(dir);
        compileToolPathEdit->setText(dir);
    });

    waveToolPathEdit = new ShowDirLineEdit(tr("GTKWave Tool Path:"), tr("Browse"), this);
    connect(waveToolPathEdit, &ShowDirLineEdit::textChanged, this, &PreferenceWidget::on_propertyChanged);
    connect(waveToolPathEdit->button(), &QPushButton::clicked, this, [=]() {
        QString dir = QFileDialog::getExistingDirectory(this, tr("select wave tool path"), m_config->lastOpenPath());
        if (dir.isEmpty()) {
            return;
        }
        m_config->setLastOpenPath(dir);
        waveToolPathEdit->setText(dir);
    });

    logEnableBox = new QCheckBox(tr("enable log output"));
    connect(logEnableBox, &QCheckBox::stateChanged, this, &PreferenceWidget::on_propertyChanged);
    loadHierarchyEnableBox = new QCheckBox(tr("load hierarchy when compilation finished"));
    connect(loadHierarchyEnableBox, &QCheckBox::stateChanged, this, &PreferenceWidget::on_propertyChanged);
    QWidget *enableWidget = new QWidget(this);
    enableWidget->setLayout(new NoMarginHBoxLayout);
    enableWidget->layout()->addWidget(logEnableBox);
    enableWidget->layout()->addWidget(loadHierarchyEnableBox);

    ivlcmdFilePathEdit = new ShowDirLineEdit(tr("iverilog command file path(-c<cmdfile>):"), tr("Browse"), this);
    connect(ivlcmdFilePathEdit, &ShowDirLineEdit::textChanged, this, &PreferenceWidget::on_propertyChanged);
    connect(ivlcmdFilePathEdit->button(), &QPushButton::clicked, this, [=]() {
        QString dir = QFileDialog::getOpenFileName(this, tr("select command file"), m_config->lastOpenPath());
        if (dir.isEmpty()) {
            return;
        }
        m_config->setLastOpenPath(dir);
        ivlcmdFilePathEdit->setText(dir);
    });

    QLabel *debugFlagLabel = new QLabel(tr("iverilog debug flags(-d<flags>):"));
    QWidget *debugFlagWidget = new QWidget;
    debugFlagWidget->setLayout(new NoMarginHBoxLayout);
    for (ProgramConfig::DebugFlag flag = ProgramConfig::scope;
         flag <= ProgramConfig::synth2;
         flag = (ProgramConfig::DebugFlag)(flag + 1)) {
        QCheckBox *box = new QCheckBox(m_config->debugFlagStr[flag]);
        connect(box, &QCheckBox::stateChanged, this, &PreferenceWidget::on_propertyChanged);
        debugFlagWidget->layout()->addWidget(box);
        ivlDebugFlagBoxes.append(box);
    }

    QLabel *generationFlagLabel = new QLabel(tr("iverilog generation flags(-g<flags>):"));
    QWidget *generationFlagWidget = new QWidget;
    NoMarginGridLayout *gridLayout = new NoMarginGridLayout;
    generationFlagWidget->setLayout(gridLayout);
    for (ProgramConfig::GenerationFlag flag = ProgramConfig::IEEE1995;
         flag <= ProgramConfig::no_shared_loop_index;
         flag = (ProgramConfig::GenerationFlag)(flag + 1)) {
        QCheckBox *box = new QCheckBox(m_config->generationFlagStr[flag]);
        connect(box, &QCheckBox::stateChanged, this, &PreferenceWidget::on_propertyChanged);
        gridLayout->addWidget(box, int(flag / 5), flag % 5);
        ivlGenerationFlagBoxes.append(box);
    }

    ivlIgnoreBox = new QCheckBox(tr("ignore missing modules(-i)"));
    connect(ivlIgnoreBox, &QCheckBox::stateChanged, this, &PreferenceWidget::on_propertyChanged);

    ivlVPIModulePathList = new ShowDirListView(tr("VPI module path(-L<path>):"),
                                               tr("Add"),
                                               tr("Remove"));
    connect(ivlVPIModulePathList->addButton(), &QPushButton::clicked, this, [=]() {
        QString dir = QFileDialog::getExistingDirectory(this, tr("select VPI module path"), m_config->lastOpenPath());
        if (dir.isEmpty()) {
            return;
        }
        m_config->setLastOpenPath(dir);
        ivlVPIModulePathList->addDirs(QStringList() << dir);
    });
    connect(ivlVPIModulePathList->removeButton(), &QPushButton::clicked, ivlVPIModulePathList, &ShowDirListView::removeSelectedDirs);
    connect(ivlVPIModulePathList, &ShowDirListView::listChanged, this, &PreferenceWidget::on_propertyChanged);

    ivlLibraryFileList = new ShowDirListView(tr("library file(-I<path>):"),
                                             tr("Add"),
                                             tr("Remove"));
    connect(ivlLibraryFileList->addButton(), &QPushButton::clicked, this, [=]() {
        QStringList dir = QFileDialog::getOpenFileNames(this, tr("select library files"), m_config->lastOpenPath());
        if (dir.isEmpty()) {
            return;
        }
        m_config->setLastOpenPath(dir.at(0));
        ivlLibraryFileList->addDirs(dir);
    });
    connect(ivlLibraryFileList->removeButton(), &QPushButton::clicked, ivlLibraryFileList, &ShowDirListView::removeSelectedDirs);
    connect(ivlLibraryFileList, &ShowDirListView::listChanged, this, &PreferenceWidget::on_propertyChanged);

    ivllibraryPathList = new ShowDirListView(tr("libraries path(-y<path>):"),
                                             tr("Add"),
                                             tr("Remove"));
    connect(ivllibraryPathList->addButton(), &QPushButton::clicked, this, [=]() {
        QString dir = QFileDialog::getExistingDirectory(this, tr("select library paths"), m_config->lastOpenPath());
        if (dir.isEmpty()) {
            return;
        }
        m_config->setLastOpenPath(dir);
        ivllibraryPathList->addDirs(QStringList() << dir);
    });
    connect(ivllibraryPathList->removeButton(), &QPushButton::clicked, ivllibraryPathList, &ShowDirListView::removeSelectedDirs);
    connect(ivllibraryPathList, &ShowDirListView::listChanged, this, &PreferenceWidget::on_propertyChanged);

    ivlOutputFileEdit = new ShowDirLineEdit(new QLabel(tr("output file name(-o<file>)('*' will be replaced by input file name):")));
    connect(ivlOutputFileEdit, &ShowDirLineEdit::textChanged, this, &PreferenceWidget::on_propertyChanged);

    QLabel *macroLabel = new QLabel(tr("set macros(-D<macro>):"), this);
    macroTable = new QTableView(this);
    macroModel = new QStandardItemModel(this);
    macroModel->setColumnCount(2);
    macroModel->setHeaderData(0, Qt::Horizontal, tr("Macro"));
    macroModel->setHeaderData(1, Qt::Horizontal, tr("Value"));
    macroTable->setModel(macroModel);
    QPushButton *addMacroButton = new QPushButton(tr("Add"), this);
    QPushButton *removeMacroButton = new QPushButton(tr("Remove"), this);
    connect(addMacroButton, &QPushButton::clicked, this, [&] {
        macroModel->appendRow(QList<QStandardItem *>()
                                  << new QStandardItem
                                  << new QStandardItem);
    });
    connect(removeMacroButton, &QPushButton::clicked, this, [&] {
        if (macroTable->currentIndex().isValid()) {
            macroModel->removeRow(macroTable->currentIndex().row());
        }
    });
    connect(macroModel, &QStandardItemModel::itemChanged, this, &PreferenceWidget::on_propertyChanged);
    QWidget *macroButtonWidget = new QWidget(this);
    macroButtonWidget->setLayout(new NoMarginVBoxLayout);
    macroButtonWidget->layout()->addWidget(addMacroButton);
    macroButtonWidget->layout()->addWidget(removeMacroButton);
    QWidget *macroWidget = new QWidget(this);
    macroWidget->setLayout(new NoMarginHBoxLayout);
    macroWidget->layout()->addWidget(macroTable);
    macroWidget->layout()->addWidget(macroButtonWidget);

    QLabel *vvpExtraLabel = new QLabel(tr("Set VVP Extra Arguments:"), this);
    vvpExtraArg = new QLineEdit(this);
    connect(vvpExtraArg, &QLineEdit::textEdited, this, &PreferenceWidget::on_propertyChanged);

    toolConfigWidget->layout()->addWidget(compileToolPathEdit);
    toolConfigWidget->layout()->addWidget(waveToolPathEdit);
    toolConfigWidget->layout()->addWidget(createHorizontalSeparator());
    toolConfigWidget->layout()->addWidget(enableWidget);
    toolConfigWidget->layout()->addWidget(createHorizontalSeparator());
    toolConfigWidget->layout()->addWidget(ivlcmdFilePathEdit);
    toolConfigWidget->layout()->addWidget(debugFlagLabel);
    toolConfigWidget->layout()->addWidget(debugFlagWidget);
    toolConfigWidget->layout()->addWidget(generationFlagLabel);
    toolConfigWidget->layout()->addWidget(generationFlagWidget);
    toolConfigWidget->layout()->addWidget(createHorizontalSeparator());
    toolConfigWidget->layout()->addWidget(ivlIgnoreBox);
    toolConfigWidget->layout()->addWidget(ivlVPIModulePathList);
    toolConfigWidget->layout()->addWidget(ivlLibraryFileList);
    toolConfigWidget->layout()->addWidget(ivllibraryPathList);
    toolConfigWidget->layout()->addWidget(ivlOutputFileEdit);
    toolConfigWidget->layout()->addWidget(macroLabel);
    toolConfigWidget->layout()->addWidget(macroWidget);
    toolConfigWidget->layout()->addWidget(vvpExtraLabel);
    toolConfigWidget->layout()->addWidget(vvpExtraArg);

    compileToolPathEdit->setText(m_config->compileToolPath());
    waveToolPathEdit->setText(m_config->waveToolPath());
    logEnableBox->setChecked(m_config->isLogEnabled());
    loadHierarchyEnableBox->setChecked(m_config->isLoadHierarchyEnabled());
    ivlcmdFilePathEdit->setText(m_config->commandFilePath());
    for (ProgramConfig::DebugFlag flag : m_config->debugFlags()) {
        ivlDebugFlagBoxes[flag]->setChecked(true);
    }
    for (ProgramConfig::GenerationFlag flag : m_config->generationFlags()) {
        ivlGenerationFlagBoxes[flag]->setChecked(true);
    }
    ivlIgnoreBox->setChecked(m_config->ifIgnoreMissingModule());
    ivlVPIModulePathList->addDirs(m_config->VPIModuleDirs());
    ivlLibraryFileList->addDirs(m_config->libraryFilePaths());
    ivllibraryPathList->addDirs(m_config->libraryDirs());
    ivlOutputFileEdit->setText(m_config->outputFileName());
    for (const QString &macro : m_config->macros()) {
        if (macro.contains('=')) {
            macroModel->appendRow(QList<QStandardItem *>()
                                      << new QStandardItem(macro.left(macro.indexOf('=')))
                                      << new QStandardItem(macro.right(macro.indexOf('=') + 1)));
        } else {
            macroModel->appendRow(QList<QStandardItem *>()
                                      << new QStandardItem(macro)
                                      << new QStandardItem);
        }
    }
    vvpExtraArg->setText(m_config->vvpExtra());
}

void PreferenceWidget::initializeFontConfig()
{
    QLabel *fontDialogLabel = new QLabel(tr("set Font:"));
    fontDialog = new QFontDialog(m_config->font());
    fontDialog->setOption(QFontDialog::NoButtons);
    connect(fontDialog, &QFontDialog::currentFontChanged, this, &PreferenceWidget::on_propertyChanged);

    QLabel *tabWidthBoxLabel = new QLabel(tr("set tab width:"));
    tabWidthBox = new QSpinBox;
    tabWidthBox->setRange(1,20);
    tabWidthBox->setValue(m_config->tabWidth());
    connect(tabWidthBox, &QSpinBox::valueChanged, this, &PreferenceWidget::on_propertyChanged);

    keywordColorEdit = new ShowColorLineEdit(tr("set keyword color:"),
                                             m_config->keywordColor(), QStringLiteral("module  always  begin  posedge"), this);
    connect(keywordColorEdit, &ShowColorLineEdit::colorChanged, this, &PreferenceWidget::on_propertyChanged);

    quotationColorEdit = new ShowColorLineEdit(tr("set quotation color:"),
                                               m_config->quotationColor(), QStringLiteral("\"This is a quotation.\""), this);
    connect(quotationColorEdit, &ShowColorLineEdit::colorChanged, this, &PreferenceWidget::on_propertyChanged);

    numberColorEdit = new ShowColorLineEdit(tr("set number color:"),
                                            m_config->numberColor(), QStringLiteral("5'b01010  6'd32  123456789"), this);
    connect(numberColorEdit, &ShowColorLineEdit::colorChanged, this, &PreferenceWidget::on_propertyChanged);

    commentColorEdit = new ShowColorLineEdit(tr("set comment color:"),
                                             m_config->commentColor(), QStringLiteral("/*This is a comment.*/ //This is a comment."), this);
    connect(commentColorEdit, &ShowColorLineEdit::colorChanged, this, &PreferenceWidget::on_propertyChanged);

    systemFunctionColorEdit = new ShowColorLineEdit(tr("set system function color:"),
                                                    m_config->systemFunctionColor(), QStringLiteral("$finish;  $dumpfile;"), this);
    connect(systemFunctionColorEdit, &ShowColorLineEdit::colorChanged, this, &PreferenceWidget::on_propertyChanged);

    fontConfigWidget->layout()->addWidget(fontDialogLabel);
    fontConfigWidget->layout()->addWidget(fontDialog);
    fontConfigWidget->layout()->addWidget(tabWidthBoxLabel);
    fontConfigWidget->layout()->addWidget(tabWidthBox);
    fontConfigWidget->layout()->addWidget(keywordColorEdit);
    fontConfigWidget->layout()->addWidget(quotationColorEdit);
    fontConfigWidget->layout()->addWidget(numberColorEdit);
    fontConfigWidget->layout()->addWidget(commentColorEdit);
    fontConfigWidget->layout()->addWidget(systemFunctionColorEdit);

}

void PreferenceWidget::initializeLanguageConfig()
{
    languageBox = new QComboBox(this);
    languageBox->addItem("中文");
    languageBox->addItem("English");
    languageBox->setCurrentIndex(m_config->languageIndex());
    connect(languageBox, &QComboBox::currentIndexChanged, this, &PreferenceWidget::on_propertyChanged);

    QLabel *languageLabel = new QLabel(tr("Select Language (Needs restart):"), this);
    languageConfigWidget->layout()->addWidget(languageLabel);
    languageConfigWidget->layout()->addWidget(languageBox);
}

void PreferenceWidget::storeSetting()
{
    m_config->setCompileToolPath(compileToolPathEdit->text());
    m_config->setWaveToolPath(waveToolPathEdit->text());
    m_config->setIsLogEnabled(logEnableBox->isChecked());
    m_config->setIsLoadHierarchyEnabled(loadHierarchyEnableBox->isChecked());
    m_config->setCommandFilePath(ivlcmdFilePathEdit->text());
    QList<ProgramConfig::DebugFlag> debugFlags;
    for (QCheckBox *box : ivlDebugFlagBoxes) {
        if (box->isChecked()) {
            debugFlags << (ProgramConfig::DebugFlag)ivlDebugFlagBoxes.indexOf(box);
        }
    }
    m_config->setdebugFlags(debugFlags);
    QList<ProgramConfig::GenerationFlag> generationFlags;
    for (QCheckBox *box : ivlGenerationFlagBoxes) {
        if (box->isChecked()) {
            generationFlags << (ProgramConfig::GenerationFlag)ivlGenerationFlagBoxes.indexOf(box);
        }
    }
    m_config->setGenerationFlags(generationFlags);
    m_config->setIfIgnoreMissingModule(ivlIgnoreBox->isChecked());
    m_config->setVPIModuleDirs(ivlVPIModulePathList->dirs());
    m_config->setLibraryFilePaths(ivlLibraryFileList->dirs());
    m_config->setLibraryDirs(ivllibraryPathList->dirs());
    m_config->setOutputFileName(ivlOutputFileEdit->text());
    int macroCount = macroModel->rowCount();
    QStringList macros;
    for (int i = 0; i < macroCount; ++i) {
        if (macroModel->item(i, 1)->data(Qt::DisplayRole).toString().isEmpty()) {
            macros << macroModel->item(i, 0)->data(Qt::DisplayRole).toString();
        } else {
            macros << macroModel->item(i, 0)->data(Qt::DisplayRole).toString() + '=' + macroModel->item(i, 1)->data(Qt::DisplayRole).toString();
        }
    }
    m_config->setMacros(macros);
    m_config->setVvpExtra(vvpExtraArg->text());

    m_config->setFont(fontDialog->currentFont());
    m_config->setTabWidth(tabWidthBox->value());    
    m_config->setKeywordColor(keywordColorEdit->color());
    m_config->setQuotationColor(quotationColorEdit->color());
    m_config->setNumberColor(numberColorEdit->color());
    m_config->setCommentColor(commentColorEdit->color());
    m_config->setSystemFunctionColor(systemFunctionColorEdit->color());

    m_config->setLanguageIndex(languageBox->currentIndex());

    m_propertyChanged = false;
    buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false);
}

QFrame *PreferenceWidget::createHorizontalSeparator()
{
    QFrame *line = new QFrame(this);
    line->setFrameShape(QFrame::HLine);
    line->setFrameShadow(QFrame::Plain);
    return line;
}

void PreferenceWidget::on_propertyChanged()
{
    m_propertyChanged = true;
    buttonBox->button(QDialogButtonBox::Apply)->setEnabled(true);
}

